package org.cos.generation.epda;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import org.cos.generation.data.DataFlowGetter;
import org.cos.generation.epda.Condition.TargetType;
import org.cos.generation.epda.action.*;
import org.cos.generation.util.info.*;
import org.cos.generation.util.predict.*;
import org.eclipse.jdt.core.dom.*;

public class EPDAGenerator {

	private DataFlowGetter dataFlowGetter;

	private ClassInfo currentClass;
	
	private MethodInfo currentMethod;
	
	private Stack<Integer> branchStateStack;
	
	private Stack<Integer> loopStateStack;
	

	public EPDAGenerator(DataFlowGetter dataFlowGetter) {
		this.dataFlowGetter = dataFlowGetter;
		this.branchStateStack = new Stack<Integer>();
		this.loopStateStack = new Stack<Integer>();
	}

	/**
	 * 访问java文件结点时
	 * @param node java文件结点
	 * @param classTable 类表
	 */
	public void enterUnit(CompilationUnit node, ClassTable classTable) {
		// 将每个类的方法表写入类表
		for (Object type : node.types()) {
			TypeDeclaration typeDec = (TypeDeclaration) type;
			String typeName = typeDec.getName().toString();
			ITypeBinding typeBinding = typeDec.resolveBinding();
			if (typeBinding != null) {
				typeName = typeBinding.getQualifiedName();
			}
			classTable.put(typeName, new ClassInfo(typeName, 
					typeDec.getMethods()));
		}
	}

	/**
	 * 访问类结点时
	 * @param node 类结点
	 * @param classTable 类表
	 */
	public void enterClass(TypeDeclaration node, ClassTable classTable) {
		ITypeBinding typeBinding = node.resolveBinding();
		String typeName = node.getName().toString();
		if (typeBinding != null) {
			typeName = typeBinding.getQualifiedName();
		}
		currentClass = classTable.get(typeName);
	}

	/**
	 * 访问方法结点时
	 * @param node 方法结点
	 */
	public void enterMethod(MethodDeclaration node) {
		EPDA epda = currentClass.getEPDA();
		Method method = Method.valueOf(node.resolveBinding());
		// 从方法表获得该方法的信息
		currentMethod = currentClass.getMethodInfo(method);
		if (currentMethod != null) {
			// 获得该方法的初始状态号
			int initState = currentMethod.getInitStateIndex();

			if (Modifier.isPublic(node.getModifiers())) {
			// 如果该方法为公有方法
				// EPDA以该方法为边由类初始状态向该方法初始链接
				epda.link(initState, method, null, null);
				// EPDA转移至该方法初始状态
				epda.trans(initState);
			} else {
			// 否则
				// 转移到该方法的初始状态
				epda.trans(initState);
			}
		} else {
			throw new NullPointerException();
		}
	}
	
	/**
	 * 离开方法结点时
	 * @param node 方法结点
	 */
	public void exitMethod(MethodDeclaration node) {
		EPDA epda = currentClass.getEPDA();
		Type returnType = node.getReturnType2();
		
		if (returnType != null && returnType.toString().equals("void") &&
				epda.getActiveStateIndex() != currentMethod.getReturnStateIndex()) {
		// 如果该方法为void方法且当前状态不是返回状态
			
			// EPDA由当前状态向返回状态链接一条空边
			epda.link(currentMethod.getReturnStateIndex(),
					null, null, null);
		}
		// 将EPDA转移至类初始状态
		epda.trans(0);
	}

	/**
	 * 进入实例化结点时
	 * @param node 实例化结点
	 */
	@SuppressWarnings("unchecked")
	public void enterInstance(ClassInstanceCreation node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		
		if (epda.getActiveStateIndex() != 
				currentMethod.getReturnStateIndex()) {
		// 如果当前状态不是该方法的返回状态
			
			// 分析构造方法
			handleInvocation(node.resolveConstructorBinding(),
					node.arguments());
		}
	}
	
	/**
	 * 进入方法调用结点时
	 * @param node 方法调用结点
	 */
	@SuppressWarnings("unchecked")
	public void enterInvocation(MethodInvocation node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		
		if (epda.getActiveStateIndex() != 
				currentMethod.getReturnStateIndex()) {
		// 如果当前状态不是该方法的返回状态
			
			// 分析调用方法
			handleInvocation(node.resolveMethodBinding(), node.arguments());
		}
	}
	
	/**
	 * 分析方法调用
	 * @param methodBinding
	 * @param paraList
	 */
	private void handleInvocation(IMethodBinding methodBinding, List<Expression> paraList) {
		Method method = Method.valueOf(methodBinding);
		MethodInfo methodInfo = currentClass.getMethodInfo(method);
		if (methodInfo != null) {
		// 如果在当前类的方法表查得到该方法
		// Custom method invocation.
			
			// 获取当前类的EPDA
			EPDA epda = currentClass.getEPDA();
			
			// 创建新状态作为下一状态
			int newStateIndex = epda.newState();
			
			// 以“下一状态号压栈”作为action
			List<Action> actions = new ArrayList<Action>();
			actions.add(new StackAction(StackAction.Operation.PUSH, 
					newStateIndex));
			// EPDA以该调用方法为边，由当前状态向所调方法的起始状态链接一条边
			epda.link(methodInfo.getInitStateIndex(), method, null, actions);
			
			// EPDA转移至调用方法的返回状态
			epda.trans(methodInfo.getReturnStateIndex());
			
			// 以“栈顶是下一状态号”为condition，弹栈作为action
			List<Condition> conditions = new ArrayList<Condition>();
			conditions.add(new Condition(TargetType.STACK, 0, 
					new EqualPredictSet(newStateIndex)));
			actions = new ArrayList<Action>();
			actions.add(new StackAction(StackAction.Operation.POP,
					newStateIndex));
			// EPDA以该方法的返回为边，由当前状态向下一状态链接一条边
			epda.link(newStateIndex, Method.valueOfReturn(method), 
					conditions, actions);
			
			// EPDA转移至下一状态
			epda.trans(newStateIndex);
		} else {
		// 否则
			if (SRMT.containsMethod(method)) {
			// 如果在SRMT表中查得到该方法
			// System method invocation.
				
				// 获取当前类的EPDA
				EPDA epda = currentClass.getEPDA();
				
				// 以调用方法参数的当前预测集合作为condition
				List<PredictSet> paraPredictList = 
						dataFlowGetter.getParaPredict(paraList);
				List<Condition> conditions = 
						new ArrayList<Condition>(paraPredictList.size());
				int i = 0;
				for (PredictSet set : paraPredictList) {
					conditions.add(new Condition(
							Condition.TargetType.PARAMETER, i++, set));
				}
				
				// EPDA以该方法为边进行生长
				epda.grow(method, conditions, null);
				// 将当前方法设置为系统调用相关的
				currentMethod.setSystemRelated();
			}
			// Others, ignore.
		}
	}

	/**
	 * 进入IF语句结点时
	 * @param node IF语句结点
	 */
	public void enterIf(IfStatement node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		
		// 将当前状态作为分支的起始状态压入栈中
		branchStateStack.push(epda.getActiveStateIndex());
	}
	
	/**
	 * 离开THEN子结点后，进入ELSE子结点语句结点前
	 * @param node IF语句结点
	 */
	public void beforeElse(IfStatement node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		// 从栈中获取分支的起始状态并弹栈
		int startState = branchStateStack.pop();
		// 将当前状态作为分支的结束状态压栈
		branchStateStack.push(epda.getActiveStateIndex());
		// EPDA转移到分支的起始状态
		epda.trans(startState);
	}
	
	/**
	 * 离开IF语句结点时.
	 * @param node IF语句结点
	 */	
	public void exitIf(IfStatement node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		
		int returnState = currentMethod.getReturnStateIndex();
		
		if (epda.getActiveStateIndex() == returnState) {
		// 如果当前状态是该方法的返回状态
			if (node.getElseStatement() != null) {
			// 如果含有ELSE子句
				// 从栈中获得分支的结束状态并弹栈
				int endState = branchStateStack.pop();
				if (endState != returnState) {
				// 如果分支结束状态不是返回状态
					// EPDA转移至分支结束状态
					epda.trans(endState);			
				}			
			} else {
			// 否则
				// 创建新状态作为分支结束状态
				int endState = epda.newState();
				// EPDA转移至分支初始状态
				epda.trans(branchStateStack.pop());
				// EPDA由分支初始状态向分支结束状态链接一条空边
				epda.link(endState,	null, null, null);
				// EPDA转移至分支结束状态
				epda.trans(endState);
			}
		} else {
		// 否则
			if (node.getElseStatement() != null) {
			// 如果含有ELSE子句
				// 从栈中获得分支的结束状态并弹栈
				int endState = branchStateStack.pop();
				// EPDA由当前状态向分支结束状态链接一条空边
				epda.link(endState,	null, null, null);
				// EPDA转移至分支结束状态
				epda.trans(endState);	
			} else {
			// 否则
				// 以当前状态作为分支结束状态
				int endState = epda.getActiveStateIndex();
				// EPDA转移至分支初始状态
				epda.trans(branchStateStack.pop());
				// EPDA由分支初始状态向分支结束状态链接一条空边
				epda.link(endState,	null, null, null);
				// EPDA转移至分支结束状态
				epda.trans(endState);
				
			}
		}
	}
	
	/**
	 * 进入DO语句结点时
	 * @param node DO语句结点
	 */
	public void enterDo(DoStatement node) {
		enterLoop(currentClass.getEPDA());
	}
	
	/**
	 * 离开DO语句结点时
	 * @param node DO语句结点
	 */
	public void exitDo(DoStatement node) {
		exitLoop(currentClass.getEPDA());
	}
	
	/**
	 * 进入WHILE语句结点时
	 * @param node WHILE语句结点
	 */
	public void enterWhile(WhileStatement node) {
		enterLoop(currentClass.getEPDA());
	}
	
	/**
	 * 离开WHILE语句结点时
	 * @param node WHILE语句结点
	 */
	public void exitWhile(WhileStatement node) {
		exitLoopJump(currentClass.getEPDA());
	}
	
	/**
	 * 进入FOR语句结点时
	 * @param node FOR语句结点
	 */
	public void enterFor(ForStatement node) {
		enterLoop(currentClass.getEPDA());
	}
	
	/**
	 * 离开FOR语句结点时
	 * @param node FOR语句结点
	 */
	public void exitFor(ForStatement node) {
		exitLoopJump(currentClass.getEPDA());
	}
	
	/**
	 * Before enter a loop.
	 * @param epda
	 * @return
	 */
	private void enterLoop(EPDA epda) {
		// 以当前状态作为循环的起始状态并压栈
		loopStateStack.push(epda.getActiveStateIndex());
	}
	
	/**
	 * Before exit a loop which execute at least once.
	 * @param epda
	 */
	private void exitLoop(EPDA epda) {
		// 从栈中获得循环起始状态并弹栈
		int startStateIndex = loopStateStack.pop();
	
		if (epda.getActiveStateIndex() != 
				currentMethod.getReturnStateIndex()) {
		// 如果当前状态不是该方法的返回状态
			
			// EPDA由当前状态向循环起始状态链接一条空边
			epda.link(startStateIndex, null, null, null);
		}
	}
	
	/**
	 * Before exit a loop which could jump.
	 * @param epda
	 */
	private void exitLoopJump(EPDA epda) {
		// 从栈中获得循环起始状态并弹栈
		int startStateIndex = loopStateStack.pop();
		
		if (epda.getActiveStateIndex() == 
			currentMethod.getReturnStateIndex()) {
		// 如果当前状态是该方法的返回状态

			// 创建新状态作为循环结束状态
			int endState = epda.newState();
			// EPDA转移至循环初始状态
			epda.trans(loopStateStack.pop());
			// EPDA由循环初始状态向分支结束状态链接一条空边
			epda.link(endState,	null, null, null);
			// EPDA转移至循环结束状态
			epda.trans(endState);
		} else {
		// 否则
			// 以当前状态作为循环结束状态
			int endStateIndex = epda.getActiveStateIndex();
			// EPDA由当前状态向循环起始状态链接一条空边
			epda.link(startStateIndex, null, null, null);
			// EPDA转移到循环起始状态
			epda.trans(startStateIndex);
			// EPDA由当前状态向循环结束状态链接一条空边
			epda.link(endStateIndex, null, null, null);
			// EPDA转移到循环结束状态
			epda.trans(endStateIndex);
		}
	}
	
	/**
	 * 进入返回结点时
	 * @param node
	 */
	public void enterReturn(ReturnStatement node) {
		// 获取当前类的EPDA
		EPDA epda = currentClass.getEPDA();
		
		// 从方法信息中获得当前方法的返回状态
		int returnState = currentMethod.getReturnStateIndex();
		// EPDA由当前状态向返回状态链接一条空边
		epda.link(returnState, null, null, null);
		// EPDA转移至返回状态
		epda.trans(returnState);
	}
	
	
	
}
